/*
 * PSP Software Development Kit - http://www.pspdev.org
 * -----------------------------------------------------------------------
 * Licensed under the BSD license, see LICENSE in PSPSDK root for details.
 *
 * Copyright (c) 2005 Jesper Svennevid
 */
#define SPIRITE_C
 
#include "libavutil/avstring.h"
#include "libavformat/avformat.h"
#include "libavformat/rtsp.h"
#include "libavdevice/avdevice.h"
#include "libswscale/swscale.h"
#include "libavcodec/audioconvert.h"
#include "libavcodec/opt.h"
#include "libavcodec/Rv34.h"


#include <pspkernel.h>
#include <pspdisplay.h>
#include <pspdebug.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <pspctrl.h>
#include <psprtc.h>

#include <pspgu.h>
#include <pspgum.h>

#include "pmp_gu.h"


struct Vertex
{
  short  u, v;
  float x, y, z;
};
PSP_MODULE_INFO("Onyx player", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER);
//PSP_HEAP_SIZE_KB(19*1024);
PSP_HEAP_SIZE_MAX();

static int exitRequest = 0;

static unsigned int __attribute__((aligned(16))) list[262144];
static unsigned int __attribute__((aligned(16))) pixels[512*272];
static unsigned int __attribute__((aligned(16))) pixels1[512*272];


unsigned char __attribute__((aligned(16))) clut256[256];
unsigned char __attribute__((aligned(16))) tex256[256*256];
#if 1
void* framebuffer = 0;
void *pmp_gu_draw_buffer = (void *) (0x04000000);
void *pmp_gu_rgb_buffer;
static unsigned int staticOffset = 0;
static void* frameBuffer[2];

#endif
#define SCREEN_WIDTH  480
#define SCREEN_HEIGHT 272

#define		PIXELSIZE	1				//in short
#define		LINESIZE	512				//in short
#define		FRAMESIZE	0x44000			//in byte
#define SLICE_SIZE 64 // Change this to experiment with different page-cache sizes

#define BUF_WIDTH (512)
#define SCR_WIDTH (480)
#define SCR_HEIGHT (272)
#define PIXEL_SIZE (4) /* change this if you change to another screenmode */
#define FRAME_SIZE (BUF_WIDTH * SCR_HEIGHT * PIXEL_SIZE)
#define ZBUF_SIZE (BUF_WIDTH SCR_HEIGHT * 2) /* zbuffer seems to be 16-bit? */

#define NUM_SLICES 128
#define NUM_ROWS 128
#define RING_SIZE 2.0f
#define RING_RADIUS 1.0f
#define SPRITE_SIZE 0.025f

#if 1

#define	PSP_SCREEN_TEXTURE_WIDTH 512
#define PSP_SCREEN_WIDTH 480
#define PSP_SCREEN_HEIGHT 272
#define	PSP_TVOUT_TEXTURE_WIDTH 768
#define PSP_TVOUT_WIDTH 720
#define PSP_TVOUT_HEIGHT 480
#define PSP_TVOUT_BUFFER_SIZE 1572864
#define PSP_TVOUT_PSPLCD_BUFFER_SIZE 835584
#define PSP_TVOUT_480P_BUFFER_SIZE 1474560
#define PSP_TVOUT_480P_LINE_SIZE 3072

//font text 

#include "font.c"

static int fontwidthtab[128] = {
	10, 10, 10, 10, 
	10, 10, 10, 10,
	10, 10, 10, 10, 
	10, 10, 10, 10,

	10, 10, 10, 10, 
	10, 10, 10, 10,
	10, 10, 10, 10,
	10, 10, 10, 10,

	10,  6,  8, 10, //   ! " #
	10, 10, 10,  6, // $ % & '
	10, 10, 10, 10, // ( ) * +
	 6, 10,  6, 10, // , - . /

	10, 10, 10, 10, // 0 1 2 3
	10, 10, 10, 10, // 6 5 8 7
	10, 10,  6,  6, // 10 9 : ;
	10, 10, 10, 10, // < = > ?

	16, 10, 10, 10, // @ A B C
	10, 10, 10, 10, // D E F G
	10,  6,  8, 10, // H I J K
	 8, 10, 10, 10, // L M N O

	10, 10, 10, 10, // P Q R S
	10, 10, 10, 12, // T U V W
	10, 10, 10, 10, // X Y Z [
	10, 10,  8, 10, // \ ] ^ _

	 6,  8,  8,  8, // ` a b c
	 8,  8,  6,  8, // d e f g
	 8,  6,  6,  8, // h i j k
	 6, 10,  8,  8, // l m n o

	 8,  8,  8,  8, // p q r s
	 8,  8,  8, 12, // t u v w
	 8,  8,  8, 10, // x y z {
	 8, 10,  8, 12  // | } ~  
};

/*
	This function draws a string on the screen
	The chars are handled as sprites.
	It supportes colors and blending.
	The fontwidth can be selected with the parameter fw, if it is 0 the best width for each char is selected.
*/
void drawString(const char* text, int x, int y, unsigned int color, int fw) {
	int len = (int)strlen(text);
	if(!len) {
		return;
	}

	typedef struct {
		float s, t;
		unsigned int c;
		float x, y, z;
	} VERT;

	VERT* v = sceGuGetMemory(sizeof(VERT) * 2 * len);

	int i;
	for(i = 0; i < len; i++) {
		unsigned char c = (unsigned char)text[i];
		if(c < 32) {
			c = 0;
		} else if(c >= 128) {
			c = 0;
		}

		int tx = (c & 0x0F) << 4;
		int ty = (c & 0xF0);

		VERT* v0 = &v[i*2+0];
		VERT* v1 = &v[i*2+1];
		
		v0->s = (float)(tx + (fw ? ((16 - fw) >> 1) : ((16 - fontwidthtab[c]) >> 1)));
		v0->t = (float)(ty);
		v0->c = color;
		v0->x = (float)(x);
		v0->y = (float)(y);
		v0->z = 0.0f;

		v1->s = (float)(tx + 16 - (fw ? ((16 - fw) >> 1) : ((16 - fontwidthtab[c]) >> 1)));
		v1->t = (float)(ty + 16);
		v1->c = color;
		v1->x = (float)(x + (fw ? fw : fontwidthtab[c]));
		v1->y = (float)(y + 16);
		v1->z = 0.0f;

		x += (fw ? fw : fontwidthtab[c]);
	}

	sceGumDrawArray(GU_SPRITES, 
		GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_2D,
		len * 2, 0, v
	);
}


void onyx_debug_wait(const char *msg,...)
{

	unsigned long key = 0;
	static unsigned long old_key = 0;
	SceCtrlData pad;
	//sceGuStart(GU_DIRECT,list);
	pspDebugScreenPrintf(msg);
	//sceGuTexSync();
	//sceGuFinish();
	//sceGuSync(0,0);	
	//disp_update();
	while (1)
	{
		sceCtrlReadBufferPositive(&pad, 1);
		if (pad.Buttons != old_key)
		{
			if (old_key)
			{
				key = old_key;
				old_key = 0;
			}
			else
			{
				key = 0;
				old_key = pad.Buttons;
			}					
		}
		if (key & PSP_CTRL_SELECT)
		{
			break;
		}
		if (key & PSP_CTRL_START)
		{
			sceKernelExitGame();			
		}
	}
}

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {

	FILE *pFile;
	char szFilename[32];
	int y;

	// Open file
	#undef sprintf;
	sprintf(szFilename, "frame%d.ppm", iFrame);
	pFile=fopen(szFilename, "wb");
	if(pFile==NULL)
		return;
	// Write header
	#undef fprintf;
	fprintf(pFile, "P6\n%d %d\n255\n", width, height);
	// Write pixel data
	for(y=0; y<height; y++)
		fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
	// Close file
	fclose(pFile);
}

#endif
int running()
{
	return !exitRequest;
}

int exitCallback(int arg1, int arg2, void *common)
{
	exitRequest = 1;
	sceKernelExitGame();

	return 0;
}

int callbackThread(SceSize args, void *argp)
{
	int cbid;

	cbid = sceKernelCreateCallback("Exit Callback", exitCallback, NULL);
	sceKernelRegisterExitCallback(cbid);

	sceKernelSleepThreadCB();

	return 0;
}

int setupCallbacks(void)
{
	int thid = 0;

	thid = sceKernelCreateThread("update_thread", callbackThread, 0x11, 0xFA0, 0, 0);
	if(thid >= 0)
	{
		sceKernelStartThread(thid, 0, 0);
	}

	return thid;
}
static void log_callback(void* ptr, int level, const char* fmt, va_list vl)
{

	unsigned long key = 0;
	static unsigned long old_key = 0;
	SceCtrlData pad;

	pspDebugScreenPrintf(fmt,vl);

	while (1)
	{
		sceCtrlReadBufferPositive(&pad, 1);
		if (pad.Buttons != old_key)
		{
			if (old_key)
			{
				key = old_key;
				old_key = 0;
			}
			else
			{
				key = 0;
				old_key = pad.Buttons;
			}					
		}
		if (key & PSP_CTRL_SELECT)
		{
			break;
		}
		if (key & PSP_CTRL_START)
		{
			sceKernelExitGame();			
		}
	}
}

//#define filename "ms0:/VIDEO/test.rmvb"
#define filename "ms0:/VIDEO/dmx2.rmvb"
unsigned int colors[8] = 
{
	0xffff0000,
	0xffff00ff,
	0xff0000ff,
	0xff00ffff,
	0xff00ff00,
	0xffffff00,
	0xffffffff,
	0xff00ffff
};
int main(int argc, char* argv[])

{
	//unsigned int i,j;
	//const char *filename = "ms0:/PSP/VIDEO/640x384_30.pmp";
  AVFormatContext *pFormatCtx;
	AVCodecContext *pCodecCtx;
  int videoStream = -1;
	AVCodec *pCodec;	
	AVFrame *pFrame ;
	AVFrame *pFrameRGB;
	uint8_t *buffer;
	int numBytes;
	int frameFinished;
	AVPacket packet;
	FILE *f;
	  FILE * infile;		/* source file */
	int error = 0;
	struct SwsContext *sws;
	unsigned long long tick_resolution = sceRtcGetTickResolution();
	unsigned long long m      = 1000;
	unsigned long long t0,t1,t2,t3,t4,t5,t0_1,t1_2,t3_4,t4_5,last_t5,t_frame;


	unsigned int i,j;
	setupCallbacks();	


	// initialize texture

	for (j = 0; j < 256; ++j)
	{
		for (i = 0; i < 256; ++i)
		{
			tex256[i + j * 256] =0x00ff007f;
		}
	}
	pspDebugScreenSetTextColor(0xffffff);
	pspDebugScreenSetBackColor(0x000000);
	pspDebugScreenInit();
	pmp_gu_init();
	pmp_gu_init_previous_values();
	int offset = 0;

   	 av_log_set_callback(log_callback);
	
	if ((infile = fopen("background.jpg", "rb")) == NULL) {
    onyx_debug_wait("can't open %s\n", "background.jpg");
  }
	if (infile!= 0) fclose(infile);


  av_register_all();



	// Open video file
	if(error = av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)!=0)
		onyx_debug_wait("Couldn't open file. error = %d\n",error); 

	// Retrieve stream information
	if(av_find_stream_info(pFormatCtx)<0)
		onyx_debug_wait("Couldn't find stream information.\n");

	// Find the first video stream
	videoStream=-1;
	for(i=0; i<pFormatCtx->nb_streams; i++)
	{
		if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO){
			videoStream=i;
			break;
		}
	}	
	if(videoStream==-1)
		onyx_debug_wait("Didn't find a video stream.\n"); 

	// Get a pointer to the codec context for the video stream
	pCodecCtx=pFormatCtx->streams[videoStream]->codec;

	// Find the decoder for the video stream
	pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
	if(pCodec==NULL)
		onyx_debug_wait("Codec not found.\n");

	// Open codec
	if(avcodec_open(pCodecCtx, pCodec)<0)
		onyx_debug_wait("Could not open codec.\n");
	
	
	
	// Allocate video frame
	pFrame=avcodec_alloc_frame();
	if(pFrame==NULL)
		onyx_debug_wait("Allocate pFrame failed.\n");

	// Allocate an AVFrame structure
	pFrameRGB=avcodec_alloc_frame();
	if(pFrameRGB==NULL)
		onyx_debug_wait("Allocate pFrameRGB failed.\n");

	// Determine required buffer size and allocate buffer
	numBytes=avpicture_get_size(PIX_FMT_RGBA, pCodecCtx->width,pCodecCtx->height);
	buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

	// Assign appropriate parts of buffer to image planes in pFrameRGB
	// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
	// of AVPicture
	avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGBA,pCodecCtx->width, pCodecCtx->height);
	onyx_debug_wait("Onyx begin decoder!\n");
	sws= sws_getContext(pCodecCtx->width,pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,PIX_FMT_RGBA, SWS_BILINEAR, NULL, NULL, NULL);
        if (sws == NULL) {
            onyx_debug_wait( "Cannot initialize the conversion context\n");
        }
	while(1)//running()
	{

		
		i=0;
		sceRtcGetCurrentTick(&last_t5);
		while((!(sceRtcGetCurrentTick(&t0)<0))&& av_read_frame(pFormatCtx, &packet)>=0 && running()) {

			// Is this a packet from the video stream
			if(packet.stream_index==videoStream) {
				// Decode video frame
				sceRtcGetCurrentTick(&t1);
				t0_1 += (t1-t0);
				avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size);
				sceRtcGetCurrentTick(&t2);
				t1_2 += (t2-t1);
				// Did we get a video frame
				if(frameFinished) {
					sceRtcGetCurrentTick(&t3);
					// Convert the image from its native format to RGB
					//img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,(AVPicture*)pFrame, pCodecCtx->pix_fmt,pCodecCtx->width, pCodecCtx->height);
					//onyx_debug_wait("file_open succes D = %d\n",4);

				       // sws_scale(sws, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
					sceRtcGetCurrentTick(&t4);
					t3_4 += (t4-t3);

					pmp_gu_start();
					pmp_gu_load_yuv(
								pCodecCtx->width,
								pCodecCtx->height,
								pFrame->data[0],
								pFrame->data[1],
								pFrame->data[2],
								pFrame->linesize[0],
								pFrame->linesize[1],
								pFrame->linesize[2]
								);

					pmp_gu_draw_yuv(0, 100, 0, 0,pixels);
					pmp_gu_wait();
					sceRtcGetCurrentTick(&t5);
					t4_5 += (t5-t4);
					
					t_frame = t5 - last_t5;

					unsigned long long t_frameall = t0_1+t1_2+t3_4+t4_5;
					
					unsigned long long fps    = t_frame   == 0 ? 1000000000 : m * tick_resolution / t_frame;
					//unsigned long long minfps = min == 0 ? 1000000000 : m * tick_resolution / min;
					//unsigned long long maxfps = max == 0 ? 1000000000 : m * tick_resolution / max;
					fps    = fps    > 1000000000 ? 1000000000 : fps;
					//minfps = minfps > 1000000000 ? 1000000000 : minfps;
					//maxfps = maxfps > 1000000000 ? 1000000000 : maxfps;
					pspDebugScreenSetXY(0, 0);
					pspDebugScreenPrintf("fps: %i.%03i  \n", (int) (fps    / m), (int) (fps    % m));
					pspDebugScreenPrintf("t0_1: %i ,  t1_2: %i ,t3_4: %i ,t4_5: %04i ", 
							(int) (t0_1*100 / t_frameall), (int) (t1_2*100 / t_frameall),(int) (t3_4*100 / t_frameall),(int) (t4_5*100 / t_frameall));
					last_t5 = t5;
					t0_1 = t1_2 = t3_4 = t4_5 = 0;
					memcpy((void*)pixels, (void *) 0x04000000, 4 * 512 * 16);
	
					sceDisplayWaitVblankStart();
					sceDisplaySetFrameBuf((void*)pixels, 512, PSP_DISPLAY_PIXEL_FORMAT_8888, PSP_DISPLAY_SETBUF_IMMEDIATE);

					i++;
				}
			//onyx_fps(i,0);
			}
			// Free the packet that was allocated by av_read_frame
			av_free_packet(&packet);

		}
	}	
	// Free the RGB image
	av_free(buffer);
	av_free(pFrameRGB);

	// Free the YUV frame
	av_free(pFrame);

	// Close the codec
	avcodec_close(pCodecCtx);

	// Close the video file
	av_close_input_file(pFormatCtx);
	sceGuTerm();
	//pgShutDown();
	sceKernelExitGame();
	return 0;
}
